package com.techsoul.module.generator.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.techsoul.common.core.constant.ApiConstants;
import com.techsoul.common.core.exception.ParamException;
import com.techsoul.common.core.pojo.vo.UserVo;
import com.techsoul.common.core.util.ServletUtils;
import com.techsoul.common.core.util.bean.BeanUtils;
import com.techsoul.common.core.util.collection.ListUtils;
import com.techsoul.common.core.util.file.FileUtils;
import com.techsoul.common.core.util.id.IdWorker;
import com.techsoul.common.core.util.lang.StringUtils;
import com.techsoul.common.core.util.text.CharsetUtils;
import com.techsoul.common.security.service.TokenService;
import com.techsoul.module.generator.constant.GenConstants;
import com.techsoul.module.generator.exception.GenException;
import com.techsoul.module.generator.mapper.GenTableMapper;
import com.techsoul.module.generator.pojo.entity.GenTable;
import com.techsoul.module.generator.pojo.param.GenTableColumnSaveParam;
import com.techsoul.module.generator.pojo.param.GenTableSaveParam;
import com.techsoul.module.generator.pojo.param.GenTableSearchParam;
import com.techsoul.module.generator.pojo.vo.GenTableColumnVo;
import com.techsoul.module.generator.pojo.vo.GenTableVo;
import com.techsoul.module.generator.pojo.vo.TableColumnVo;
import com.techsoul.module.generator.service.IGenTableColumnService;
import com.techsoul.module.generator.service.IGenTableService;
import com.techsoul.module.generator.util.GenUtils;
import com.techsoul.module.generator.util.VelocityInitializer;
import com.techsoul.module.generator.util.VelocityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 业务 服务层实现
 *
 * @author liuen
 */
@Slf4j
@Service
@Transactional(readOnly = true)
public class GenTableServiceImpl implements IGenTableService {

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private GenTableMapper mapper;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private IGenTableColumnService genTableColumnService;

    /**
     * 新增
     *
     * @param params
     * @return
     */
    @Transactional(readOnly = false)
    @Override
    public String insert(GenTableSaveParam params) {
        UserVo user = tokenService.getLoginUser(ServletUtils.getRequest()).getUser();
        GenTable entity = new GenTable();
        BeanUtils.copyProperties(params, entity);
        entity.setId(idWorker.nextId() + "").preInsert(user.getId());
        this.mapper.insert(entity);
        return entity.getId();
    }

    /**
     * 删除
     *
     * @param id
     * @return
     */
    @Transactional(readOnly = false)
    @Override
    public Integer delete(String id) {
        genTableColumnService.deleteByTableIds(Arrays.asList(id));
        return this.mapper.deleteById(id);
    }

    /**
     * 批量删除
     *
     * @param idList
     */
    @Override
    @Transactional(readOnly = false)
    public Integer batchDelete(List<String> idList) {
        genTableColumnService.deleteByTableIds(idList);
        if (ListUtils.isNotEmpty(idList)) {
            return this.mapper.deleteBatchIds(idList);
        } else {
            return 0;
        }
    }

    /**
     * 修改
     *
     * @param params
     * @return
     */
    @Transactional(readOnly = false)
    @Override
    public Integer update(GenTableSaveParam params) {
        UserVo user = tokenService.getLoginUser(ServletUtils.getRequest()).getUser();
        // 更新代码生成表
        GenTable entityFromDb = this.mapper.selectById(params.getId());
        if (entityFromDb == null) {
            throw new ParamException("id=" + params.getId());
        }
        this.validateEdit(params);
        BeanUtils.copyProperties(params, entityFromDb);
        entityFromDb.preUpdate(user.getId());
        int count = this.mapper.updateById(entityFromDb);

        // 更新代码生成表字段
        if (count > 0) {
            List<GenTableColumnVo> columns = params.getColumns();
            if (ListUtils.isNotEmpty(columns)) {
                columns.forEach(column -> {
                    GenTableColumnSaveParam genTableColumnSaveParam = new GenTableColumnSaveParam();
                    BeanUtils.copyProperties(column, genTableColumnSaveParam);
                    genTableColumnService.update(genTableColumnSaveParam);
                });
            }
        }
        return count;
    }

    /**
     * 详情
     *
     * @param id
     * @return
     */
    @Override
    public GenTableVo findById(String id) {
        GenTableVo genTableVo = mapper.findById(id);
        setTableFromOptions(genTableVo);
        return genTableVo;
    }

    @Override
    public List<TableColumnVo> findTableColumnList(String tableName) {
        List<TableColumnVo> tableColumnList = genTableColumnService.findTableColumnList(tableName);
        if (ListUtils.isNotEmpty(tableColumnList)) {
            for (TableColumnVo column : tableColumnList) {
                column.setTableName(tableName)
                        .setJavaField(StringUtils.toCamelCase(column.getColumnName()));
            }
        }
        return tableColumnList;
    }

    /**
     * 列表
     *
     * @param params
     * @return
     */
    @Override
    public List<GenTableVo> findAll(GenTableSearchParam params) {
        return mapper.findAll(params);
    }

    /**
     * 列表-分页
     *
     * @param params
     * @param current
     * @param pageSize
     * @return
     */
    @Override
    public IPage<GenTableVo> findAll(GenTableSearchParam params, Integer current, Integer pageSize) {
        return mapper.findAll(new Page<>(current, pageSize), params);
    }

    /**
     * 数据库表-列表
     *
     * @param params
     * @return
     */
    @Override
    public List<GenTableVo> findDbTableAll(GenTableSearchParam params) {
        return mapper.findDbTableAll(params);
    }

    /**
     * 数据库表-列表-分页
     *
     * @param params
     * @param current
     * @param pageSize
     * @return
     */
    @Override
    public IPage<GenTableVo> findDbTableAll(GenTableSearchParam params, Integer current, Integer pageSize) {
        return mapper.findDbTableAll(new Page<>(current, pageSize), params);
    }

    /**
     * 导入数据库表结构
     *
     * @param tables 数据库表名（多个用逗号隔开）
     */
    @Override
    @Transactional
    public void importGenTable(String tables) {
        // 查询数据库表信息
        GenTableSearchParam genTableSearchParam = new GenTableSearchParam();
        genTableSearchParam.setTableNameList(Arrays.asList(tables.split(",")));
        List<GenTableVo> tableList = this.findDbTableAll(genTableSearchParam);
//        String operName = SecurityUtils.getUsername();
        try {
            for (GenTableVo table : tableList) {
                String tableName = table.getTableName();
//                GenUtils.initTable(table, operName);
                GenUtils.initTable(table, "operName");
                GenTableSaveParam genTableSaveParam = new GenTableSaveParam();
                BeanUtils.copyProperties(table, genTableSaveParam);
                String tableId = this.insert(genTableSaveParam);
                if (StringUtils.isNotBlank(tableId)) {
                    // 保存数据库表字段信息
                    List<GenTableColumnVo> genTableColumns = genTableColumnService.findDbTableColumnByTableName(tableName);
                    for (GenTableColumnVo column : genTableColumns) {
                        // 初始化字段信息
                        GenUtils.initColumnField(column, table);
                        GenTableColumnSaveParam genTableColumnSaveParam = new GenTableColumnSaveParam();
                        BeanUtils.copyProperties(column, genTableColumnSaveParam);
                        genTableColumnSaveParam.setTableId(tableId);
                        // 页面不需要添加的字段，isRequired属性设置为N
                        if (GenUtils.arraysContains(GenConstants.COLUMN_NAME_NOT_ADD, column.getColumnName())) {
                            genTableColumnSaveParam.setIsRequired(ApiConstants.N);
                        }
                        genTableColumnService.insert(genTableColumnSaveParam);
                    }
                }
            }
        } catch (Exception e) {
            log.error("导入失败：", e);
            throw new GenException("导入失败：" + e.getMessage());
        }
    }

    /**
     * 预览代码
     *
     * @param id
     * @return
     */
    @Override
    public Map<String, String> previewCode(String id) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        // 查询表信息
        GenTableVo table = mapper.findById(id);
        // 查询列信息
        List<GenTableColumnVo> columns = table.getColumns();
        // 设置表主键
        this.setPkColumn(table, columns);
        // 初始化Velocity模板引擎
        VelocityInitializer.initVelocity();
        // 设置模板引擎变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTableType());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            try {
                Template tpl = Velocity.getTemplate(template, ApiConstants.UTF8);
                tpl.merge(context, sw);
                dataMap.put(template, sw.toString());
            } catch (Exception e) {
                throw new GenException("渲染模板失败，表名：" + table.getTableName());
            }
        }
        return dataMap;
    }

    /**
     * 生成代码-自定义路径，不生成SQL和前端代码
     *
     * @param tableName
     */
    @Override
    public void genCode(String tableName) {
        // 查询表信息
        GenTableVo table = mapper.findByTableName(tableName);
        // 查询列信息
        List<GenTableColumnVo> columns = table.getColumns();
        // 设置表主键
        this.setPkColumn(table, columns);
        // 初始化Velocity模板引擎
        VelocityInitializer.initVelocity();
        // 设置模板引擎变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTableType());
        for (String template : templates) {
            if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) {
                // 渲染模板
                StringWriter sw = new StringWriter();
                try {
                    Template tpl = Velocity.getTemplate(template, ApiConstants.UTF8);
                    tpl.merge(context, sw);
                    String path = this.getGenPath(table, template);
                    FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetUtils.UTF_8);
                } catch (Exception e) {
                    throw new GenException("渲染模板失败，表名：" + table.getTableName());
                }
            }
        }
    }

    /**
     * 生成代码-zip
     */
    @Override
    public void genCode(String tableName, ZipOutputStream zip) {
        // 查询表信息
        GenTableVo table = mapper.findByTableName(tableName);
        // 查询列信息
        List<GenTableColumnVo> columns = table.getColumns();
        // 设置表主键
        this.setPkColumn(table, columns);
        // 初始化Velocity模板引擎
        VelocityInitializer.initVelocity();
        // 设置模板引擎变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTableType());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            try {
                Template tpl = Velocity.getTemplate(template, ApiConstants.UTF8);
                tpl.merge(context, sw);
                // 添加到zip
                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
                IOUtils.write(sw.toString(), zip, ApiConstants.UTF8);
                IOUtils.closeQuietly(sw);
                zip.flush();
                zip.closeEntry();
            } catch (Exception e) {
                log.error("渲染模板失败，表名：" + table.getTableName(), e);
            }
        }
    }

    /**
     * 下载代码
     *
     * @param tableName
     * @param response
     */
    @Override
    public void downloadCode(String tableName, HttpServletResponse response) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        genCode(tableName, zip);
        IOUtils.closeQuietly(zip);
        this.downloadToZip(response, outputStream.toByteArray());
    }

    /**
     * 批量下载代码
     *
     * @param tableNames
     */
    @Override
    public void downloadCode(String[] tableNames, HttpServletResponse response) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        for (String tableName : tableNames) {
            genCode(tableName, zip);
        }
        IOUtils.closeQuietly(zip);
        this.downloadToZip(response, outputStream.toByteArray());
    }

    /**
     * 同步数据库表结构
     *
     * @param tableName
     */
    @Override
    @Transactional
    public void syncDbTable(String tableName) {
        // 查询代码生成表的信息
        GenTableVo table = mapper.findByTableName(tableName);
        List<GenTableColumnVo> tableColumns = table.getColumns();
        List<String> tableColumnNames = tableColumns.stream().map(GenTableColumnVo::getColumnName).collect(Collectors.toList());
        // 查询数据库表的信息
        List<GenTableColumnVo> dbTableColumns = genTableColumnService.findDbTableColumnByTableName(tableName);
        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumnVo::getColumnName).collect(Collectors.toList());
        // 新增数据库表新增的字段
        dbTableColumns.forEach(column -> {
            if (!tableColumnNames.contains(column.getColumnName())) {
                GenUtils.initColumnField(column, table);
                GenTableColumnSaveParam genTableColumnSaveParam = new GenTableColumnSaveParam();
                BeanUtils.copyProperties(column, genTableColumnSaveParam);
                genTableColumnService.insert(genTableColumnSaveParam);
            }
        });
        // 删除数据库表已删除的字段
        List<GenTableColumnVo> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(delColumns)) {
            List<String> idList = delColumns.stream().map(column -> column.getId()).collect(Collectors.toList());
            genTableColumnService.batchDelete(idList);
        }
    }

    /**
     * 校验保存参数
     *
     * @param genTable
     */
    private void validateEdit(GenTableSaveParam genTable) {
//        if (GenConstants.TABLE_TYPE_TREE.equals(genTable.getTableType())) {
//            String options = JSON.toJSONString(genTable.getParams());
//            JSONObject paramsObj = JSONObject.parseObject(options);
//            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
//                throw new GenException("树编码字段不能为空");
//            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
//                throw new GenException("树父编码字段不能为空");
//            } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
//                throw new GenException("树名称字段不能为空");
//            }
//        }
    }

    /**
     * 设置主键列信息
     *
     * @param table
     * @param columns
     */
    private void setPkColumn(GenTableVo table, List<GenTableColumnVo> columns) {
        for (GenTableColumnVo column : columns) {
            if (column.isPk()) {
                table.setPkColumn(column);
                break;
            }
        }
        /**
         * 如果数据库表没有设置主键，则默认第一个字段为主键
         */
        if (StringUtils.isNull(table.getPkColumn())) {
            table.setPkColumn(columns.get(0));
        }
    }

    /**
     * 设置代码生成其他选项值
     *
     * @param genTable
     */
    private void setTableFromOptions(GenTableVo genTable) {
        JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions());
        if (StringUtils.isNotNull(paramsObj)) {
            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
            String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
            String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);

            genTable.setTreeCode(treeCode);
            genTable.setTreeParentCode(treeParentCode);
            genTable.setTreeName(treeName);
            genTable.setParentMenuId(parentMenuId);
            genTable.setParentMenuName(parentMenuName);
        }
    }

    /**
     * 获取代码生成地址
     *
     * @param table
     * @param template
     * @return
     */
    private String getGenPath(GenTableVo table, String template) {
        String genPath = table.getGenPath();
        if (StringUtils.equals(genPath, "/")) {
            return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
        }
        return genPath + File.separator + VelocityUtils.getFileName(template, table);
    }

    /**
     * 下载为zip文件
     *
     * @param response
     * @param data
     */
    private void downloadToZip(HttpServletResponse response, byte[] data) {
        response.reset();
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.setHeader("Content-Disposition", "attachment; filename=\"GenTable.zip\"");
        response.addHeader("Content-Length", "" + data.length);
        response.setContentType("application/octet-stream; charset=UTF-8");
        try {
            IOUtils.write(data, response.getOutputStream());
        } catch (IOException e) {
            log.error("生成代码失败", e);
            throw new GenException(e);
        }
    }

}
